﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using MyFloridaHouse.libSvCommonUtilities;
using MyFloridaHouse.libSvOpenDistricts.Criteria;
using MyFloridaHouse.libSvOpenDistricts.Districts;
using MyFloridaHouse.libSvOpenDistricts.enumerations;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvUtilities.Logging;
using System.Windows.Controls;
using MyFloridaHouse.libSvOpenDistricts.Census;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using System.Windows.Data;

namespace MyFloridaHouse.libSvOpenDistricts.SummaryDataGrid
{
    public delegate void SummaryDataGridDataChangedEventHandler(EventArgs e);

    public partial class SummaryDataGridManager : INotifyPropertyChanged
    {

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        #region events  SummaryDataGridDataChanged
        public static event SummaryDataGridDataChangedEventHandler SummaryDataGridDataChanged;

        public static void RaiseSummaryDataGridDataChanged()
        {
            if (SummaryDataGridDataChanged != null) { SummaryDataGridDataChanged(new EventArgs()); }
        }
        #endregion

        #region properties

        private static SummaryDataGridManager _summaryDataGridInstance;  // for outside reference through the static class


        #region Constructor
        public SummaryDataGridManager(ServiceDAL serviceDAL, DataGrid UIDataGridForDistrictSummaries)
        {
            SummaryDataGridManager._summaryDataGridInstance = this;
            this.serviceDAL = serviceDAL;
            this.UIDataGridForDistrictSummaries = UIDataGridForDistrictSummaries;
            LoadCriteriaDataset(Constants.DefaultCriteriaDatasetName);
            IntializeSummaryDataGridCommands();
            dataGridView = new PagedCollectionView(dataGridList);
        }
        #endregion

        // these ID's define the data we want to get from the database for each district
        private DataDictionaryIDList summaryDataGridDataIDs = new DataDictionaryIDList();

        public string activeLayerIDList
        {
            get { return summaryDataGridDataIDs.layerIDList; }
        }

        public string activeDictionaryIDList
        {
            get { return summaryDataGridDataIDs.dictionaryIDList; }
        }

        // this describes the final data to show
        private List<DataGridColumnDescription> _summaryDataGridColumnDescriptionList = new List<DataGridColumnDescription>();
        public List<DataGridColumnDescription> summaryDataGridColumnDescriptionList
        {
            get { return _summaryDataGridColumnDescriptionList; }
        }

        // used internally to keep the district row data 
        // dataGridList doesn't have district 0, so all indexes 1 less than districtID
        private ObservableCollection<SummaryDataGridRow> _dataGridList = new ObservableCollection<SummaryDataGridRow>();
        public ObservableCollection<SummaryDataGridRow> dataGridList
        {
            get
            {
                return _dataGridList;
            }
            set
            {
                if (_dataGridList != value)
                {
                    _dataGridList = value;
                    NotifyPropertyChanged("dataGridList");
                }
            }
        }

        private PagedCollectionView _dataGridView;
        public PagedCollectionView dataGridView
        {
            get { return _dataGridView; }
            set
            {
                if (_dataGridView != value)
                {
                    _dataGridView = value;
                    NotifyPropertyChanged("dataGridView");
                }
            }
        }
        private enumDataGridMode _dataGridMode = enumDataGridMode.District;
        public enumDataGridMode dataGridMode
        {
            get { return _dataGridMode; }
            set
            {
                if (_dataGridMode != value)
                {
                    _dataGridMode = value;
                    ChangeDataGridSort();
                    NotifyPropertyChanged("dataGridMode");
                }
            }
        }

        private int _workingPopulationDeviation = -99999;
        public int workingPopulationDeviation
        {
            get 
            {
                return _workingPopulationDeviation;
            }
            set
            {
                if (_workingPopulationDeviation != value)
                {
                    _workingPopulationDeviation = value;

                    NotifyPropertyChanged("workingPopulationDeviation");
                }
            }
        }

        private string _criteriaDatasetName;
        public string criteriaDatasetName 
        { 
            get 
            { 
                return _criteriaDatasetName; 
            }
            set 
            {
                _criteriaDatasetName = value;
                NotifyPropertyChanged("criteriaDatasetName"); 
            } 
        }
        
        #endregion

        private int _districtCount = 0;
        //private int _totalPopulation = Constants.TOTAL_POPULATION_TO_BE_APPORTIONED;
        private int _dataGridTotalPopluationIndex;  //initialized when criteria changes
        private int _defaultDistrictTargetPopulation = 0;

        private int TpopDevGridColumnIndex = 1;  // because it's the second column created in ReplaceSummaryDataGridDataRow()

        private DataGrid UIDataGridForDistrictSummaries;
        
        private ServiceDAL serviceDAL;

        #region public static methods (this is how the application interfaces with the Summary Data Grid)

        public static void Initialize(int districtCount)
        {
            _summaryDataGridInstance.Reset(districtCount);
        }

        //public static void SetTotalPopulation(int newTotalPopulation)
        //{
        //    _summaryDataGridInstance._totalPopulation = newTotalPopulation;
        //}

        public static void SetTargetPopulation(int defaultTargetPopulation)
        {
            _summaryDataGridInstance._defaultDistrictTargetPopulation = defaultTargetPopulation;
            _summaryDataGridInstance.UpdateDistrictPopulationDeviationAll(defaultTargetPopulation);
        }

        public static void SetTargetPopulation(int districtID, int targetPopulation)
        {
             _summaryDataGridInstance._dataGridList[GetDataGridListDistrictIDIndex(districtID)].targetPopulation = targetPopulation;
        }

        public static int GetTargetPopulation(int districtID)
        {
            return _summaryDataGridInstance._dataGridList[GetDataGridListDistrictIDIndex(districtID)].targetPopulation;
        }

        public static void BuildAllDistrictSummaries()
        {
            _summaryDataGridInstance.ResetLastChangedTimeStamp();
            _summaryDataGridInstance.BuildDistrictSummaryAll();
        }

        public static void BuildChangedDistrictSummaries(List<int> changedDistrictsList, List<string> condensedDistrictList)
        {
            foreach (var districtID in changedDistrictsList)
            {
                _summaryDataGridInstance.GetDistrictDataSummary(districtID, condensedDistrictList[districtID]);
            }
        }

        public static void ResetAllChangedIndicators()
        {
            _summaryDataGridInstance.ResetLastChanged();
        }

        public static int GetPopulationDeviation(int districtID)
        {
            return _summaryDataGridInstance.GetDistrictPopulationDeviation(districtID);
        }
        #endregion








        #region private static methods

        private static int GetDataGridListDistrictIDIndex(int districtID)
        {
            // This may be overkill, but it's REAL easy to forget to subtract 1 when you're using the dataGridList
            return districtID-1;
        }

        private static int FindPopulationColumnIndex(List<DataGridColumnDescription> columnList)
        {
            int populationColumnIndex = -1;
            bool foundPopulationIndex = false;
            foreach (var col in columnList)
            {
                populationColumnIndex++;
                if (IsDataGridColumnTotalPopulation(col))
                {
                    foundPopulationIndex = true;
                    break;
                }
            }
            if (!foundPopulationIndex)
            {
                Log.NotifyUser("The Summary Grid criteria you specified doesn't have Census Total Population in it.  District deviation from desired population will not work.");
            }
            return populationColumnIndex;
        }

        private static bool IsDataGridColumnTotalPopulation(DataGridColumnDescription col)
        {
            bool result = false;
            if (col != null)
            {
                // NOTE: We are looking in our column description list
                // if criteria is null, it's reserved for either districtID or population deviation,
                // therefore, it won't be in the list of data coming back from the database, 
                // so don't increment our index of the database result list
                if (col.critera != null)
                {
                    if (col.critera.dictionaryList != null)
                    {
                        if (col.critera.dictionaryList.Count == 1)
                        {
                            if ((col.critera.dictionaryList[0].dictionaryID == Constants.PopulationDictionaryID)
                               & (col.critera.dictionaryList[0].layerID == Constants.PopulationDictionaryIDSourceLayerID))
                            {
                                result = true;
                            }
                        }
                    }
                }
            }
            return result;
        }
        #endregion
        public void LoadCriteriaDataset(string newCriteriaDatasetName)
        {
            if (criteriaDatasetName != newCriteriaDatasetName)
            {
                criteriaDatasetName = newCriteriaDatasetName;
                _summaryDataGridColumnDescriptionList = DataGridColumnBuilder.BuildDataGridColumnDescriptions(criteriaDatasetName);
                _dataGridTotalPopluationIndex = FindPopulationColumnIndex(summaryDataGridColumnDescriptionList);
                summaryDataGridDataIDs = CriteriaDataset.GetUniqueDictionaryIDListFromDatasetName(criteriaDatasetName);
                SetUpSummaryDataGridColumns();
            }
        }

        public int GetDistrictPopulationDeviation(int districtID)
        {
            if ((0 < districtID) & (districtID <= dataGridList.Count))
            {
                return dataGridList[GetDataGridListDistrictIDIndex(districtID)].valuesList[TpopDevGridColumnIndex].intValue;
            }
            else
            {
                return int.MaxValue;
            }
        }

        private void SetUpSummaryDataGridColumns()
        {
            DataGridColumnBuilder.BuildDataGridColumns(UIDataGridForDistrictSummaries, summaryDataGridColumnDescriptionList);
            UIDataGridForDistrictSummaries.FrozenColumnCount = 3;  // freezes first three columns (DistrictID, Population Deviation, Tpop)
            if (_districtCount > 0)
            {
                BuildDistrictSummaryAll();
            }
        }

        private void BuildDistrictSummaryAll()
        {
            List<string> condensedDistrictList = CensusGeometryCondensed.ToCondensedList(_districtCount);
            for (int districtID = 1; districtID <= _districtCount; districtID++)
            {

                    GetDistrictDataSummary(districtID, condensedDistrictList[districtID]);

            }
        }

        private void GetDistrictDataSummary(int districtID, string condensedDistrictList)
        {
            // no point in getting the data summary if there is no geography in the district
            if (condensedDistrictList.Length > 0)
            {
                if ((districtID > 0) & (districtID <= dataGridList.Count))  
                {
                    serviceDAL.GetDistrictDataSummary(districtID
                                                     , condensedDistrictList
                                                     , activeLayerIDList
                                                     , activeDictionaryIDList
                                                     , new System.EventHandler<GetDistrictDataSummaryCompletedEventArgs>(GetDistrictDataSummaryCompleted));
                }
            }
            else
            {
                // force the data grid row to display zero's when there aren't any shapes assigned to the district
                ReplaceSummaryDataGridDataRow(districtID
                                            , new ObservableCollection<RegionDataDTO>());
            }
        }

        private void GetDistrictDataSummaryCompleted(object sender, GetDistrictDataSummaryCompletedEventArgs args)
        {
            serviceDAL.getDistrictDataSummaryRequestCount--;

            if (!ServiceDAL.IsError("GetDistrictDataSummaryCompleted", args.Error, args.Result, args.Result.errorMessage))
            {
                try
                {
                    ReplaceDistrictDataSummary(args.Result);
                }
                catch (Exception ex)
                {
                    Log.HandleInternalError("Unable to replace district summary: " + ex.Message.ToString());
                }
            }
        }

        private void ReplaceDistrictDataSummary(DistrictDataSummarySTO result)
        {
            if (result.districtDataSummaryList == null)
            {
                // todo: log this error, should never happen
                Log.HandleInternalError("Database returned a null districtDataSummaryList for District ID " + result.districtID.ToString());
            }
            else
            {

                // don't display the shape for the default (unassigned) district (district 0)
                ReplaceSummaryDataGridDataRow(result.districtID
                                            , result.districtDataSummaryList);
            }
        }

        private void ResetLastChanged()
        {
            foreach (var dist in dataGridList)
            {
                dist.lastChanged = false;
            }
        }

        private void ResetLastChangedTimeStamp()
        {
            foreach (var dist in dataGridList)
            {
                dist.lastChangedTimeStamp = DateTime.MinValue;
            }
        }

        private void Reset(int districtCount)
        {
            if (districtCount > 0)
            {
                if (_districtCount != districtCount)
                {
                    // we are creating a new plan with a different district count, so target populations must change
                    _defaultDistrictTargetPopulation = Constants.TOTAL_POPULATION_TO_BE_APPORTIONED / districtCount;
                }

                _districtCount = districtCount;
                dataGridList.Clear();

                for (int districtID = 1; districtID <= districtCount; districtID++)
                {
                    List<SummaryDataGridRowValue> valuesList = new List<SummaryDataGridRowValue>();

                    // for each district, loop through all the columns to create values for the data grid columns
                    foreach (var col in summaryDataGridColumnDescriptionList)
                    {

                        valuesList.Add(new  SummaryDataGridRowValue());
                    }
                    // first column of data grid is always districtID
                    valuesList[0].intValue = districtID;


                    SummaryDataGridRow newRow = new SummaryDataGridRow();
                    newRow.valuesList = valuesList;
                    newRow.districtID = districtID;
                    newRow.targetPopulation = _defaultDistrictTargetPopulation;
                    newRow.lastChanged = false;
                    newRow.lastChangedTimeStamp = DateTime.Today;
                    dataGridList.Add(newRow);
                    UpdateSummaryDataGridDataRowPopulationDeviation(districtID);
                }

            }
        }

        private static int GetDistrictTotalPopulation(ObservableCollection<RegionDataDTO> districtDataSummaryList)
        {
            int Tpop = 0;
            var districtPopulationQuery = from districtDataSummaryValues in districtDataSummaryList
                                          where districtDataSummaryValues.dictionaryID == Constants.PopulationDictionaryID
                                              & districtDataSummaryValues.sourceLayerID == Constants.PopulationDictionaryIDSourceLayerID
                                          select districtDataSummaryValues.dataValue;
            if (districtPopulationQuery.Count() > 0)
            {
                string tpopDataValue = districtPopulationQuery.First();
                Int32.TryParse(tpopDataValue, out Tpop);
            }
            else
            {
                Tpop = 0;
            }
            return Tpop;
        }

        private void ReplaceSummaryDataGridDataRow(int districtID, ObservableCollection<RegionDataDTO> districtDataSummaryList)
        {
            if ((districtID >= 1) & (districtID <= dataGridList.Count()))
            {
                int oldRowTargetPopulation = _dataGridList[GetDataGridListDistrictIDIndex(districtID)].targetPopulation;
                

                SummaryDataGridRow newRow = new SummaryDataGridRow();
                newRow.valuesList = new List<SummaryDataGridRowValue>();
                newRow.districtID = districtID;
                newRow.targetPopulation = oldRowTargetPopulation;
                newRow.lastChanged = true;

                newRow.lastChangedTimeStamp = SetLastChangedTimeStamp(districtID);
                

                if (districtID > dataGridList.Count())
                {
                    Log.HandleInternalError("Unable to display district data summary for district [" + districtID.ToString() + "],Only [" + dataGridList.Count().ToString() + "] districts defined for data grid");
                    return;
                }

                bool noDataReceived = false;
                if (districtDataSummaryList == null)
                    noDataReceived = true;
                else if (districtDataSummaryList.Count() == 0)
                    noDataReceived = true;

                if (noDataReceived)
                {
                    // got no data to display so just fill it with blanks
                    newRow.valuesList.Clear();
                    foreach (var col in summaryDataGridColumnDescriptionList)
                    {
                        newRow.valuesList.Add(new SummaryDataGridRowValue());
                    }

                    newRow.valuesList[0].intValue = districtID;
                    newRow.valuesList[1].intValue = -oldRowTargetPopulation; // population deviation
                }
                else
                {
                    // Population Deviation
                    int Tpop = GetDistrictTotalPopulation(districtDataSummaryList);
                    int TpopDev = Tpop - oldRowTargetPopulation;

                    // add the special columns for data grid
                    newRow.valuesList.Add(new SummaryDataGridRowValue(districtID)); // districtID
                    newRow.valuesList.Add(new SummaryDataGridRowValue(TpopDev));

                    foreach (var col in summaryDataGridColumnDescriptionList)
                    {

                        if (col.critera != null)
                        {
                            if (DataGridColumnDescriptionValueType.floatDataType == col.valueType)
                            {
                                float regionComputedDataValueFloat = 0.0f;
                                CriteriaDataset.EvaluateFloatCriteria(districtDataSummaryList.ToList(), col.critera, false, out regionComputedDataValueFloat);
                                newRow.valuesList.Add(new SummaryDataGridRowValue(regionComputedDataValueFloat));
                            }
                            else
                            {
                                int regionComputedDataValueInt = 0;
                                CriteriaDataset.EvaluateIntCriteria(districtDataSummaryList.ToList(), col.critera, false, out regionComputedDataValueInt);
                                newRow.valuesList.Add(new SummaryDataGridRowValue(regionComputedDataValueInt));
                            }
                        }
                    }
                }

                dataGridList[GetDataGridListDistrictIDIndex(districtID)] = newRow;

                UpdateWorkingPopulationDeviation();
                RaiseSummaryDataGridDataChanged();
            }
        }

        private DateTime SetLastChangedTimeStamp(int districtID)
        {
            DateTime lastChangedTimeStamp;

            SummaryDataGridRow oldRow = dataGridList[GetDataGridListDistrictIDIndex(districtID)];
            if (oldRow.lastChangedTimeStamp == DateTime.MinValue)
            {
                // 
                // we are doing a file load, we want all districts to load with same changed timestamp
                // So, On file load we call ResetLastChangedTimeStamp() to set all dates to minValue
                // Then here we can check it and set as appropriate
                DateTime maxTime = (from row in dataGridList
                                    select row.lastChangedTimeStamp).Max();
                if (DateTime.MinValue == maxTime)
                {
                    // this is the first district loaded
                    lastChangedTimeStamp = DateTime.Now;
                }
                else
                {
                    // other districts have loaded, set this time to be same as other districts
                    lastChangedTimeStamp = maxTime;
                }
            }
            else
            {
                lastChangedTimeStamp = DateTime.Now;
            }

            return lastChangedTimeStamp;
        }

        private void UpdateDistrictPopulationDeviationAll(int targetPopulation)
        {
            for (var districtID = 1; districtID <= dataGridList.Count(); districtID++)
            {
                dataGridList[GetDataGridListDistrictIDIndex(districtID)].targetPopulation = targetPopulation;
                UpdateSummaryDataGridDataRowPopulationDeviation(districtID);
            }
        }

        private void UpdateSummaryDataGridDataRowPopulationDeviation(int districtID)
        {
            try
            {
                int Tpop = dataGridList[GetDataGridListDistrictIDIndex(districtID)].valuesList[_dataGridTotalPopluationIndex].intValue;
                int targetPopulation = dataGridList[GetDataGridListDistrictIDIndex(districtID)].targetPopulation;
                int TpopDev = Tpop - targetPopulation;
                dataGridList[GetDataGridListDistrictIDIndex(districtID)].valuesList[TpopDevGridColumnIndex].intValue = TpopDev;

                UpdateWorkingPopulationDeviation();
            }
            catch (Exception ex)
            {
                Log.HandleException(new Exception("Unable to update district Population Deviation for the datagrid, each row will update when that district changes :" + ex.Message));
            }
        }


        private void ChangeDataGridSort()
        {
            switch (_dataGridMode)
            {
                case enumDataGridMode.Changed:
                    dataGridView.SortDescriptions.Clear();
                    dataGridView.SortDescriptions.Add(new SortDescription("lastChangedTimeStamp",ListSortDirection.Descending));
                    dataGridView.SortDescriptions.Add(new SortDescription("districtID", ListSortDirection.Ascending));
                    break;

                default:
                    dataGridView.SortDescriptions.Clear();
                    dataGridView.SortDescriptions.Add(new SortDescription("districtID",ListSortDirection.Ascending));
                    break;
            }


        }

        public void UpdateWorkingPopulationDeviation()
        {
            const int populationDeviationGridColumnIndex = 1;
            int workingDeviation = 0;

            foreach (var gridRow in dataGridList)
            {
                try
                {
                    int districtPopulation = gridRow.valuesList[_dataGridTotalPopluationIndex].intValue;
                    // ignore a district if it doesn't have any population
                    if (districtPopulation > 0)
                    {
                        workingDeviation += gridRow.valuesList[populationDeviationGridColumnIndex].intValue;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Unable to calculate total population deviation for the datagrid");
                    workingDeviation = -99990;
                    break;
                }
            }
            workingPopulationDeviation = workingDeviation;
        }
    }

}

